package com.jshan.service.impl;

import com.jshan.base.dto.CodeGenRequest;
import com.jshan.service.CodeGenService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

@Service
public class CodeGenServiceImpl implements CodeGenService {
    private static final String TEMPLATE_BASE = "templates/codegen/";

    private static final Map<String, String> typeToTemplate = new HashMap<>();
    static {
        typeToTemplate.put("entity", "entity.java.ftl");
        typeToTemplate.put("controller", "controller.java.ftl");
        typeToTemplate.put("service", "service.java.ftl");
        typeToTemplate.put("serviceImpl", "serviceImpl.java.ftl");
        typeToTemplate.put("mapper", "mapper.java.ftl");
        typeToTemplate.put("xml", "mapper.xml.ftl");
        typeToTemplate.put("vue-list", "vue-list.vue.ftl");
        typeToTemplate.put("vue-form", "vue-form.vue.ftl");
    }

    private Configuration getFreemarkerConfig() throws IOException {
        Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
        cfg.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "/");
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }

    private Map<String, Object> buildModel(CodeGenRequest req) {
        Map<String, Object> model = new HashMap<>();
        String className = toCamelCase(req.getTableName(), true);
        String packageName = "com.jshan.modules." + req.getTableName().toLowerCase();
        model.put("className", className);
        model.put("packageName", packageName);
        model.put("tableName", req.getTableName());
        model.put("tableComment", req.getTableComment());
        model.put("fields", convertFields(req.getFields()));
        model.put("indexes", req.getIndexes());
        return model;
    }

    private List<Map<String, Object>> convertFields(List<?> fields) {
        List<Map<String, Object>> list = new ArrayList<>();
        for (Object f : fields) {
            Map<String, Object> m = new HashMap<>();
            try {
                // 反射兼容 DTO
                for (var field : f.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    m.put(field.getName(), field.get(f));
                }
                // 类型映射
                String dbType = (String) m.get("type");
                m.put("javaType", mapJavaType(dbType));
            } catch (Exception ignore) {
            }
            list.add(m);
        }
        return list;
    }

    private String mapJavaType(String dbType) {
        if (dbType == null)
            return "String";
        dbType = dbType.toLowerCase();
        if (dbType.contains("int"))
            return "Integer";
        if (dbType.contains("bigint"))
            return "Long";
        if (dbType.contains("char") || dbType.contains("text"))
            return "String";
        if (dbType.contains("date") || dbType.contains("time"))
            return "java.time.LocalDateTime";
        if (dbType.contains("decimal") || dbType.contains("float") || dbType.contains("double"))
            return "java.math.BigDecimal";
        return "String";
    }

    @Override
    public Map<String, String> preview(CodeGenRequest request) {
        Map<String, String> result = new LinkedHashMap<>();
        try {
            Configuration cfg = getFreemarkerConfig();
            Map<String, Object> model = buildModel(request);
            for (String type : request.getTypes()) {
                String tpl = typeToTemplate.get(type);
                if (tpl == null)
                    continue;
                Template template = cfg.getTemplate(TEMPLATE_BASE + tpl);
                StringWriter sw = new StringWriter();
                template.process(model, sw);
                result.put(type, sw.toString());
            }
        } catch (IOException | TemplateException e) {
            throw new RuntimeException("代码预览失败: " + e.getMessage(), e);
        }
        return result;
    }

    @Override
    public void generate(CodeGenRequest request) {
        Map<String, String> codeMap = preview(request);
        // 路径校验
        if (request.getBackendPath() != null && !request.getBackendPath().isEmpty()) {
            checkAndCreateDir(request.getBackendPath());
        }
        if (request.getFrontendPath() != null && !request.getFrontendPath().isEmpty()) {
            checkAndCreateDir(request.getFrontendPath());
        }
        // 写文件
        for (Map.Entry<String, String> entry : codeMap.entrySet()) {
            String type = entry.getKey();
            String code = entry.getValue();
            String filePath = getFilePath(type, request, code);
            if (filePath != null) {
                try {
                    Files.createDirectories(Paths.get(filePath).getParent());
                    try (Writer out = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8")) {
                        out.write(code);
                    }
                } catch (IOException e) {
                    throw new RuntimeException("写入文件失败: " + filePath, e);
                }
            }
        }
    }

    private void checkAndCreateDir(String path) {
        File dir = new File(path);
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new RuntimeException("无法创建目录: " + path);
            }
        }
        if (!dir.canWrite()) {
            throw new RuntimeException("目录不可写: " + path);
        }
    }

    private String getFilePath(String type, CodeGenRequest req, String code) {
        String className = toCamelCase(req.getTableName(), true);
        String backend = req.getBackendPath();
        String frontend = req.getFrontendPath();
        switch (type) {
            case "entity":
                return backend + "/entity/" + className + ".java";
            case "controller":
                return backend + "/controller/" + className + "Controller.java";
            case "service":
                return backend + "/service/" + className + "Service.java";
            case "serviceImpl":
                return backend + "/service/impl/" + className + "ServiceImpl.java";
            case "mapper":
                return backend + "/mapper/" + className + "Mapper.java";
            case "xml":
                return backend + "/mapper/xml/" + className + "Mapper.xml";
            case "vue-list":
                return frontend + "/" + req.getTableName().toLowerCase() + "/list.vue";
            case "vue-form":
                return frontend + "/" + req.getTableName().toLowerCase() + "/form.vue";
            default:
                return null;
        }
    }

    private String toCamelCase(String s, boolean upperFirst) {
        String[] parts = s.split("[_-]");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            String part = parts[i];
            if (i == 0 && !upperFirst) {
                sb.append(part.substring(0, 1).toLowerCase()).append(part.substring(1));
            } else {
                sb.append(part.substring(0, 1).toUpperCase()).append(part.substring(1));
            }
        }
        return sb.toString();
    }
}
